| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- "use server";
- import { getServerSideConfig } from "@/app/config/server";
- import {
- TENCENT_BASE_URL,
- ApiPath,
- ModelProvider,
- ServiceProvider,
- Tencent,
- } from "@/app/constant";
- import { prettyObject } from "@/app/utils/format";
- import { NextRequest, NextResponse } from "next/server";
- import { auth } from "@/app/api/auth";
- import { isModelAvailableInServer } from "@/app/utils/model";
- import * as crypto from "node:crypto";
- const serverConfig = getServerSideConfig();
- async function handle(
- req: NextRequest,
- { params }: { params: { path: string[] } },
- ) {
- console.log("[Tencent Route] params ", params);
- if (req.method === "OPTIONS") {
- return NextResponse.json({ body: "OK" }, { status: 200 });
- }
- const authResult = auth(req, ModelProvider.Hunyuan);
- if (authResult.error) {
- return NextResponse.json(authResult, {
- status: 401,
- });
- }
- try {
- const response = await request(req);
- return response;
- } catch (e) {
- console.error("[Tencent] ", e);
- return NextResponse.json(prettyObject(e));
- }
- }
- export const GET = handle;
- export const POST = handle;
- async function request(req: NextRequest) {
- const controller = new AbortController();
- // tencent just use base url or just remove the path
- let path = `${req.nextUrl.pathname}`.replaceAll(
- ApiPath.Tencent + "/" + Tencent.ChatPath,
- "",
- );
- let baseUrl = serverConfig.tencentUrl || TENCENT_BASE_URL;
- if (!baseUrl.startsWith("http")) {
- baseUrl = `https://${baseUrl}`;
- }
- if (baseUrl.endsWith("/")) {
- baseUrl = baseUrl.slice(0, -1);
- }
- console.log("[Proxy] ", path);
- console.log("[Base Url]", baseUrl);
- const timeoutId = setTimeout(
- () => {
- controller.abort();
- },
- 10 * 60 * 1000,
- );
- const fetchUrl = `${baseUrl}${path}`;
- const body = await req.text();
- const fetchOptions: RequestInit = {
- headers: {
- ...getHeader(body),
- },
- method: req.method,
- body,
- redirect: "manual",
- // @ts-ignore
- duplex: "half",
- signal: controller.signal,
- };
- try {
- const res = await fetch(fetchUrl, fetchOptions);
- // to prevent browser prompt for credentials
- const newHeaders = new Headers(res.headers);
- newHeaders.delete("www-authenticate");
- // to disable nginx buffering
- newHeaders.set("X-Accel-Buffering", "no");
- return new Response(res.body, {
- status: res.status,
- statusText: res.statusText,
- headers: newHeaders,
- });
- } finally {
- clearTimeout(timeoutId);
- }
- }
- // 使用 SHA-256 和 secret 进行 HMAC 加密
- function sha256(message: any, secret = "", encoding?: string) {
- return crypto.createHmac("sha256", secret).update(message).digest(encoding);
- }
- // 使用 SHA-256 进行哈希
- function getHash(message: any, encoding = "hex") {
- return crypto.createHash("sha256").update(message).digest(encoding);
- }
- function getDate(timestamp: number) {
- const date = new Date(timestamp * 1000);
- const year = date.getUTCFullYear();
- const month = ("0" + (date.getUTCMonth() + 1)).slice(-2);
- const day = ("0" + date.getUTCDate()).slice(-2);
- return `${year}-${month}-${day}`;
- }
- function getHeader(payload: any) {
- // https://cloud.tencent.com/document/api/1729/105701
- // 密钥参数
- const SECRET_ID = serverConfig.tencentSecretId;
- const SECRET_KEY = serverConfig.tencentSecretKey;
- const endpoint = "hunyuan.tencentcloudapi.com";
- const service = "hunyuan";
- const region = ""; // optional
- const action = "ChatCompletions";
- const version = "2023-09-01";
- const timestamp = Math.floor(Date.now() / 1000);
- //时间处理, 获取世界时间日期
- const date = getDate(timestamp);
- // ************* 步骤 1:拼接规范请求串 *************
- const hashedRequestPayload = getHash(payload);
- const httpRequestMethod = "POST";
- const contentType = "application/json";
- const canonicalUri = "/";
- const canonicalQueryString = "";
- const canonicalHeaders =
- `content-type:${contentType}\n` +
- "host:" +
- endpoint +
- "\n" +
- "x-tc-action:" +
- action.toLowerCase() +
- "\n";
- const signedHeaders = "content-type;host;x-tc-action";
- const canonicalRequest = [
- httpRequestMethod,
- canonicalUri,
- canonicalQueryString,
- canonicalHeaders,
- signedHeaders,
- hashedRequestPayload,
- ].join("\n");
- // ************* 步骤 2:拼接待签名字符串 *************
- const algorithm = "TC3-HMAC-SHA256";
- const hashedCanonicalRequest = getHash(canonicalRequest);
- const credentialScope = date + "/" + service + "/" + "tc3_request";
- const stringToSign =
- algorithm +
- "\n" +
- timestamp +
- "\n" +
- credentialScope +
- "\n" +
- hashedCanonicalRequest;
- // ************* 步骤 3:计算签名 *************
- const kDate = sha256(date, "TC3" + SECRET_KEY);
- const kService = sha256(service, kDate);
- const kSigning = sha256("tc3_request", kService);
- const signature = sha256(stringToSign, kSigning, "hex");
- // ************* 步骤 4:拼接 Authorization *************
- const authorization =
- algorithm +
- " " +
- "Credential=" +
- SECRET_ID +
- "/" +
- credentialScope +
- ", " +
- "SignedHeaders=" +
- signedHeaders +
- ", " +
- "Signature=" +
- signature;
- return {
- Authorization: authorization,
- "Content-Type": contentType,
- Host: endpoint,
- "X-TC-Action": action,
- "X-TC-Timestamp": timestamp.toString(),
- "X-TC-Version": version,
- "X-TC-Region": region,
- };
- }
|